﻿using Common;
using Common.FrontEnd;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Util.Props;
using FrontEnd.Denoise;
using FrontEnd.Transform;
using System.IO;

namespace FrontEnd
{
    /**
    /// Cepstrum is an auto-configurable DataProcessor which is used to compute a
    /// specific cepstrum (for a target acoustic model) given the spectrum. The
    /// Cepstrum is computed using a pipeline of front end components which are
    /// selected, customized or ignored depending on the feat.params file which
    /// characterizes the target acoustic model for which this cepstrum is computed.
    /// A typical legacy MFCC Cepstrum will use a MelFrequencyFilterBank, followed
    /// by a DiscreteCosineTransform. A typical denoised MFCC Cepstrum will use a
    /// MelFrequencyFilterBank, followed by a Denoise component, followed by a
    /// DiscreteCosineTransform2, followed by a Lifter component. The
    /// MelFrequencyFilterBank parameters (numberFilters, minimumFrequency and
    /// maximumFrequency) are auto-configured based on the values found in
    /// feat.params.
    /// 
    /// @author Horia Cucu
     */
    public class AutoCepstrum: BaseDataProcessor
    {
        /**
        /// The property specifying the acoustic model for which this cepstrum will
        /// be configured. For this acoustic model (AM) it is mandatory to specify a
        /// location in the configuration file. The Cepstrum will be configured
        /// based on the feat.params file that will be found in the specified AM
        /// location.
         */
        [S4Component(type = typeof(ILoader))]
        public static String PROP_LOADER = "loader";
        protected ILoader loader;

        /**
        /// The filter bank which will be used for creating the cepstrum. The filter
        /// bank is always inserted in the pipeline and its minimum frequency,
        /// maximum frequency and number of filters are configured based on the
        /// "lowerf", "upperf" and "nfilt" values in the feat.params file of the
        /// target acoustic model.
         */
        protected BaseDataProcessor filterBank;

        /**
        /// The denoise component which could be used for creating the cepstrum. The
        /// denoise component is inserted in the pipeline only if
        /// "-remove_noise yes" is specified in the feat.params file of the target
        /// acoustic model.
         */
        protected Denoise.Denoise denoise;

        /**
        /// The property specifying the DCT which will be used for creating the
        /// cepstrum. If "-transform legacy" is specified in the feat.params file of
        /// the target acoustic model or if the "-transform" parameter does not
        /// appear in this file at all, the legacy DCT component is inserted in the
        /// pipeline. If "-transform dct" is specified in the feat.params file of
        /// the target acoustic model, then the current DCT component is inserted in
        /// the pipeline.
         */
        protected DiscreteCosineTransform dct;

        /**
        /// The lifter component which could be used for creating the cepstrum. The
        /// lifter component is inserted in the pipeline only if
        /// "-lifter <lifterValue>" is specified in the feat.params file of the
        /// target acoustic model.
         */
        protected Lifter lifter;

        /**
        /// The list of <code>DataProcessor</code>s which were auto-configured for
        /// this Cepstrum component.
         */
        protected List<IDataProcessor> selectedDataProcessors;

        public AutoCepstrum(ILoader loader)
        {
            this.loader = loader;
            loader.load();
            initDataProcessors();
        }

        public AutoCepstrum() {
        }

        /*
        /// (non-Javadoc)
        /// @see
        /// edu.cmu.sphinx.util.props.Configurable#newProperties(edu.cmu.sphinx.util
        /// .props.PropertySheet)
         */
        override
        public void newProperties(PropertySheet ps)
        {
            base.newProperties(ps);
            loader = (ILoader) ps.getComponent(PROP_LOADER);
            try {
                loader.load();
            } 
            catch (IOException e) 
            {
                throw new PropertyException(e);
            }
            initDataProcessors();
        }

        private void initDataProcessors() 
        {
            //try {
            //    Dictionary<String, Object> featParams = loader.getProperties();
            //    selectedDataProcessors = new List<IDataProcessor>();

            //    double lowFreq = parseDouble(featParams["-lowerf"]);
            //    double hiFreq = parseDouble(featParams["-upperf"]);
            //    int numFilter = parseInt(featParams["-nfilt"]);

            //    // TODO: should not be there, but for now me must preserve
            //    // backward compatibility with the legacy code.
            //    if (loader is KaldiLoader)
            //        filterBank = new MelFrequencyFilterBank2(lowFreq,
            //                                                 hiFreq,
            //                                                 numFilter);
            //    else
            //        filterBank = new MelFrequencyFilterBank(lowFreq,
            //                                                hiFreq,
            //                                                numFilter);

            //    selectedDataProcessors.add(filterBank);

            //    if ((featParams.get("-remove_noise") != null)
            //            && (featParams.get("-remove_noise").Equals("yes"))) 
            //    {
            //        denoise = new Denoise.Denoise();
            //        // denoise.newProperties();
            //        denoise.setPredecessor(selectedDataProcessors
            //                .get(selectedDataProcessors.size() - 1));
            //        selectedDataProcessors.add(denoise);
            //    }

            //    //if ((featParams.get("-transform") != null)
            //    //        && (featParams.get("-transform").equals("dct"))) {
            //    //    dct = new DiscreteCosineTransform2(
            //    //                                       numFilter,
            //    //                                       DiscreteCosineTransform.class
            //    //                                               .getField("PROP_CEPSTRUM_LENGTH")
            //    //                                               .getAnnotation(S4Integer.class)
            //    //                                               .defaultValue());
            //    //} 
            //    //else if ((featParams.get("-transform") != null)
            //    //        && (featParams.get("-transform").equals("kaldi")))
            //    //{
            //    //    dct = new KaldiDiscreteCosineTransform(
            //    //                                           numFilter,
            //    //                                           DiscreteCosineTransform.class
            //    //                                                   .getField("PROP_CEPSTRUM_LENGTH")
            //    //                                                   .getAnnotation(S4Integer.class)
            //    //                                                   .defaultValue());
            //    //} else {
            //    //    dct = new DiscreteCosineTransform(numFilter,
            //    //                                      DiscreteCosineTransform.class
            //    //                                              .getField("PROP_CEPSTRUM_LENGTH")
            //    //                                              .getAnnotation(S4Integer.class)
            //    //                                              .defaultValue());
            //    //}
            //    dct.setPredecessor(selectedDataProcessors
            //            .get(selectedDataProcessors.size() - 1));
            //    selectedDataProcessors.add(dct);

            //    if (featParams.get("-lifter") != null) {
            //        lifter = new Lifter(Integer.parseInt((String) featParams
            //                .get("-lifter")));
            //        lifter.setPredecessor(selectedDataProcessors
            //                .get(selectedDataProcessors.size() - 1));
            //        selectedDataProcessors.add(lifter);
            //    }
            //    Trace.WriteLine("Cepstrum component auto-configured as follows: "
            //            + toString());
            //} 
            //catch (Exception exc) 
            //{
            //    throw new Exception(exc);
            //}
        }

        /*
        /// (non-Javadoc)
        /// @see
        /// edu.cmu.sphinx.frontend.DataProcessor#initialize(edu.cmu.sphinx.frontend
        /// .CommonConfig)
         */
        public void initialize() 
        {
            base.initialize();

            foreach (IDataProcessor dataProcessor in selectedDataProcessors)
                dataProcessor.initialize();
        }

        /**
        /// Returns the processed Data output, basically calls
        /// <code>getData()</code> on the last processor.
        /// 
        /// @return a Data object that has been processed by the cepstrum
        /// @throws DataProcessingException if a data processor error occurs
         */
        public IData getData()
        {
            IDataProcessor dp;
            dp = selectedDataProcessors[selectedDataProcessors.Count - 1];
            return dp.getData();
        }

        /**
        /// Sets the predecessor for this DataProcessor. The predecessor is actually
        /// the spectrum builder.
        /// 
        /// @param predecessor the predecessor of this DataProcessor
         */
        public void setPredecessor(IDataProcessor predecessor) 
        {
            filterBank.setPredecessor(predecessor);
        }

        /**
        /// Returns a description of this Cepstrum component in the format:
        /// <cepstrum name> {<DataProcessor1>, <DataProcessor2> ...
        /// <DataProcessorN>}
        /// 
        /// @return a description of this Cepstrum
         */
        override
        public String ToString() 
        {
            StringBuilder description = new StringBuilder(base.ToString())
                    .Append(" {");
            foreach (IDataProcessor dp in selectedDataProcessors)
                description.Append(dp).Append(", ");
            description.Length = description.Length - 2;
            return description.Append('}').ToString();
        }

    }
}
